/**
 * 
 */
package com.google.code.cisx.store;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.code.cisx.model.CommonUpdatableModelEntity;
import com.google.code.cisx.store.api.EntityNotFoundException;
import com.google.code.cisx.store.api.Query;
import com.google.code.cisx.store.api.Store;
import com.google.code.cisx.store.api.StoreException;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
import com.google.inject.TypeLiteral;

/**
 * @author <a href='mailto:rahul.thakur.xdev@gmail.com'>Rahul Thakur</a>
 * @version $Id: StoreService.java 258 2008-11-29 06:23:51Z rahul.thakur.xdev $
 * @since 1.0
 */
public class StoreService {

  private static final Log log = LogFactory.getLog(StoreService.class);

  @Inject
  private static Injector injector;

  private static final Object mutex = new Object();

  /**
   * Cached {@link Store} instances keyed on Entity Class.
   */
  private static Map<Class<? extends CommonUpdatableModelEntity>, Provider<?>> cache = new HashMap<Class<? extends CommonUpdatableModelEntity>, Provider<?>>();

  /**
   * 
   * @param <T>
   * @param klass
   * @param id
   * @return
   * @throws EntityNotFoundException
   * @throws StoreException
   */
  public static <T extends CommonUpdatableModelEntity> T lookup(Class<T> klass, Long id)
      throws EntityNotFoundException, StoreException {
    return getStoreInstance(klass).lookup(klass, id);
  }

  /**
   * 
   * @param <T>
   * @param klass
   * @param entity
   * @return
   * @throws StoreException
   */
  public static <T extends CommonUpdatableModelEntity> T save(Class<T> klass, T entity) throws StoreException {
    return getStoreInstance(klass).save(entity);
  }

  /**
   * 
   * @param <T>
   * @param klass
   * @param entity
   * @throws StoreException
   */
  public static <T extends CommonUpdatableModelEntity> void delete(Class<T> klass, T entity) throws StoreException {
    getStoreInstance(klass).delete(entity);
  }

  public static <T extends CommonUpdatableModelEntity> List<T> query(Class<T> klass, Query<T> query, int start,
      int range) throws StoreException {
    return getStoreInstance(klass).query(query, start, range);
  }

  /**
   * 
   * @param <T>
   * @param entityKlass
   * @return
   */
  @SuppressWarnings("unchecked")
  private static <T extends CommonUpdatableModelEntity> Store<T, Query<T>> getStoreInstance(final Class<T> entityKlass) {
    long startTime = System.nanoTime();

    try {
      final ParameterizedType parameterizedQueryType = new ParameterizedType() {

        public Type[] getActualTypeArguments() {
          return new Type[] { entityKlass };
        }

        public Type getOwnerType() {
          return null;
        }

        public Type getRawType() {
          return Query.class;
        }
      };

      ParameterizedType parameterizedStoreType = new ParameterizedType() {

        public Type[] getActualTypeArguments() {
          // Here we tell the runtime that one of these Type arguments is
          // another ParameterizedType. Example: Store<Project, Query<Project>>
          return new Type[] { entityKlass, parameterizedQueryType };
        }

        public Type getOwnerType() {
          return null;
        }

        public Type getRawType() {
          return Store.class;
        }
      };

      TypeLiteral<?> typeLiteral = TypeLiteral.get(parameterizedStoreType);

      // Check if there is a Store Provider available 
      if (null == cache.get(entityKlass)) {
        if (injector.findBindingsByType(typeLiteral).size() > 0) {
          Provider<?> provider = injector.findBindingsByType(typeLiteral).get(0).getProvider();          
          synchronized (mutex) {
            // again check if there is a Store provider still not available 
            if (null == cache.get(entityKlass))
              cache.put(entityKlass, provider);
          }
        }
      }

      Store<T, Query<T>> store = (Store<T, Query<T>>) cache.get(entityKlass).get();
      
      return store;

    } finally {
      long endTime = System.nanoTime();
      log.debug(String.format("%s nanoseconds taken to obtain Store Provider instance for %s entity", (endTime - startTime),
          entityKlass.getSimpleName()));
    }
  }
}
